package cn.tb.ymcc.service.impl;

import cn.tb.ymcc.domain.Course;
import cn.tb.ymcc.domain.CourseMarket;
import cn.tb.ymcc.domain.CourseOrder;
import cn.tb.ymcc.domain.CourseOrderItem;
import cn.tb.ymcc.dto.KillCourseOrderInfosDto;
import cn.tb.ymcc.dto.KillPreOrderParamsDto;
import cn.tb.ymcc.dto.OrderInfosDto;
import cn.tb.ymcc.dto.PayDto;
import cn.tb.ymcc.exception.GlobleException;
import cn.tb.ymcc.feign.CourseUserLearnFeignClient;
import cn.tb.ymcc.mapper.CourseOrderMapper;
import cn.tb.ymcc.result.JSONResult;
import cn.tb.ymcc.service.ICourseOrderItemService;
import cn.tb.ymcc.service.ICourseOrderService;
import cn.tb.ymcc.util.AssertUtil;
import cn.tb.ymcc.util.CodeGenerateUtils;
import cn.tb.ymcc.vo.CourseItemVo;
import cn.tb.ymcc.vo.CourseOrderVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.easysdk.payment.common.models.AlipayTradeCloseResponse;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.alipay.easysdk.factory.Factory;

import java.util.Date;
import java.util.HashMap;
import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author tangbo
 * @since 2022-07-29
 */
@Service
@Slf4j
public class CourseOrderServiceImpl extends ServiceImpl<CourseOrderMapper, CourseOrder> implements ICourseOrderService {

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;
    @Autowired
    private CourseUserLearnFeignClient courseUserLearnFeignClient;
    @Autowired
    private ICourseOrderItemService courseOrderItemService;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    @Autowired
    private RedissonClient redissonClient;
    /**
     * 保存订单
     * 1.参数校验 JRS303
     * 2.校验token
     * 3.业务校验
     *  ----课程必须上架，课程id不能重复
     * 4.保存订单
     *   4.1保存主订单
     *   4.2保存子订单
     *   4.3修改主订单金额
     * 5.删除token
     * @param dto
     */
    @Override
    @Transactional
    public String placeOrder(OrderInfosDto dto) {
        //参数校验
        //校验token
        Long loginId = 1L;
        // org.apache.commons.lang.StringUtils
        String courseIdsStr = StringUtils.join(dto.getCourseIds(), ",");
        String key = courseIdsStr+":"+loginId;
        Object tokenTmp = redisTemplate.opsForValue().get(key);
        AssertUtil.isNotNull(tokenTmp,"token失效，请重新下单");
        AssertUtil.isEqualsIgnoreCase(dto.getToken(),tokenTmp.toString(),"token不一致,请重新下单");
        //业务校验----课程必须上架，课程id不能重复
        //为service-course编写Feign接口，将查询的订单结算页的Vo暴露出来
        //service-order 通过Feign接口去调用service-course获取，课程相关的数据
        JSONResult jsonResult = courseUserLearnFeignClient.courseOrderInfo(courseIdsStr);
        AssertUtil.isTrue(jsonResult.isSuccess(),"信息错误，下单失败！");
        AssertUtil.isNotNull(jsonResult.getData(),"服务不可达，下单失败！");

        String objStr = JSON.toJSONString(jsonResult.getData());
        CourseOrderVo courseOrderVo = JSONObject.parseObject(objStr, CourseOrderVo.class);
        List<CourseItemVo> courseInfos = courseOrderVo.getCourseInfos();
        AssertUtil.isNotNull(courseInfos,"课程信息错误，下单失败！");
        //保存订单
        //主订单保存
        Date date = new Date();
        CourseOrder order = new CourseOrder();
        order.setCreateTime(date);
        order.setOrderNo(CodeGenerateUtils.generateOrderSn(loginId));
        order.setStatusOrder(0);
        order.setUserId(loginId);
        order.setVersion(0);
        order.setPayType(dto.getPayType());
        //insert(order);

        //子订单保存
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        courseInfos.forEach(courseInfo->{
            Course course = courseInfo.getCourse();
            CourseMarket courseMarket = courseInfo.getCourseMarket();
            CourseOrderItem orderItem = new CourseOrderItem();
            //orderItem.setOrderId(order.getId());
            orderItem.setAmount(courseMarket.getPrice());
            orderItem.setCount(1);
            orderItem.setCreateTime(date);
            orderItem.setCourseName(course.getName());
            orderItem.setCoursePic(course.getPic());
            orderItem.setCourseId(course.getId());
            orderItem.setVersion(order.getVersion());
            orderItem.setOrderNo(order.getOrderNo());
            //设置主订单金额
            order.setTotalAmount(order.getTotalAmount().add(orderItem.getAmount()));
            //设置主订单主题
            sb.append(course.getName()).append(",");
            //设置主订单total_count
            order.setTotalCount(order.getTotalCount()+1);
            //courseOrderItemService.insert(orderItem);
            //把主订单中的子订单加到主订单中List<CourseOrderItem> items，一起传给mq
            order.getItems().add(orderItem);
        });
        sb.append("]");
        sb.append("支付:"+order.getTotalAmount());
        order.setTitle(sb.toString());
        //修改主订单金额
        //updateById(order);
        HashMap<Object, Object> extParam = new HashMap<>();
        extParam.put("userId",loginId);
        extParam.put("courseId",courseIdsStr);
        //向mq发送事务消息==========================
        PayDto payDto = new PayDto(
                order.getTotalAmount(),
                order.getPayType(),
                null,
                order.getOrderNo(),
                order.getUserId(),
                JSON.toJSONString(extParam),
                order.getTitle()
                );
        log.info("发送事务消息");
        //构建消息体
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(payDto)).build();
        TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(
                "service-order-producer",
                "course-order-topic:course-order-tag",
                //消息体
                message,
                //额外参数
                order
        );
        //发行消息后的事务状态检查
        log.info("发送完事务消息--检查事务消息状态");
        LocalTransactionState localTransactionState = transactionSendResult.getLocalTransactionState();
        SendStatus sendStatus = transactionSendResult.getSendStatus();
        if(localTransactionState != LocalTransactionState.COMMIT_MESSAGE || sendStatus != SendStatus.SEND_OK ){
            log.info("检查事务状态消息异常");
            throw new GlobleException("下单失败！！");
        }
        //==========发送事务消息结束

        //发送支付超时延迟消息
        try {
            SendResult sendResult = rocketMQTemplate.syncSend(
                    "paytimeout-topic:paytimeout-tag",//topic:tag
                    MessageBuilder.withPayload(order.getOrderNo()).build(),//消息体
                    2000,//发送延迟消息的超时时间
                    16//延迟等级  如果是30分钟 16
            );
            SendStatus sendStatus1 = sendResult.getSendStatus();
            if(sendStatus1 != SendStatus.SEND_OK){
            /*
              先尝试重试3次，如果3次重试都发不出去
                1.记录一个log表
                2.发站内信，发短信，发邮件通知运维人员，人工接入操作
             */
            }
        } catch (Exception e) {
            e.printStackTrace();
            //报错，我们需要做补偿机制
            /*
             先尝试重试3次，如果3次重试都发不出去
             1.记录一个log表
             2.发站内信，发短信，发邮件通知运维人员，人工接入操作
             */
        }
        //删除防重token
        redisTemplate.delete(key);
        System.out.println(order.getOrderNo());
        return order.getOrderNo();

    }

    /**
     * 保存订单和子订单
     * @param courseOrder
     */
    @Override
    public void saveOrderAndItems(CourseOrder courseOrder) {
        //查询是否已经下过单了
        CourseOrder orderTmp = selectOrderByOrderNo(courseOrder.getOrderNo());
        AssertUtil.isNull(orderTmp,"订单已经存在");
        //保存主订单
        insert(courseOrder);
        //保存子订单
        courseOrder.getItems().forEach(item->{
            //设置子订单订单id
            item.setOrderId(courseOrder.getId());
            courseOrderItemService.insert(item);
        });

    }

    /**
     * 根据orderNo查询订单
     * @param orderNo
     * @return
     */
    @Override
    public CourseOrder selectOrderByOrderNo(String orderNo) {
        EntityWrapper<CourseOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("order_no", orderNo);
        return selectOne(wrapper);
    }

    /**
     * 支付成功
     * @param courseOrder
     */
    @Override
    public void updatePayOrder(CourseOrder courseOrder) {
        courseOrder.setStatusOrder(CourseOrder.PAY_SUCCSEE);
        updateById(courseOrder);
        //支付成功之后还是要删除Redis中的预创订单
        /*KillPreOrderParamsDto preOrder = (KillPreOrderParamsDto)redisTemplate.opsForValue().get(courseOrder.getOrderNo());
        redisTemplate.delete(preOrder.getOrderNo());*/
    }

    /**
     * 支付超时自动取消订单
     * @param orderNo
     */
    @Override
    public void cancelOrder(String orderNo) {
        //应该拿着订单号去支付宝查询一波，看订单是否支付成功
        CourseOrder courseOrder = selectOrderByOrderNo(orderNo);
        AssertUtil.isNotNull(courseOrder,"订单不存在");
        if(courseOrder.getStatusOrder().intValue() != CourseOrder.PAY_SUCCSEE.intValue()){
            courseOrder.setStatusOrder(CourseOrder.PAY_CANCEL);
            updateById(courseOrder);
        }
        //支付超时取消的时候，如果是秒杀订单，需要还库存
        // @TODO  秒杀还库存----  支付成功删除预创订单
        KillPreOrderParamsDto preOrder = (KillPreOrderParamsDto)redisTemplate.opsForValue().get(orderNo);
        String key = preOrder.getActivityId()+":"+preOrder.getCourseId();
        if(preOrder != null){
            RSemaphore semaphore = redissonClient.getSemaphore(key);//获取唯一的信号量对象
            Integer killCount = 1;//默认秒杀一个，如果是秒杀多个，前端传递过来
            boolean tryAcquire = semaphore.trySetPermits(killCount);
        }
        //删除删除Redis中的预创订单
        redisTemplate.delete(orderNo);


        //调用支付宝，关闭支付
        try {
            AlipayTradeCloseResponse close = Factory.Payment.Common().close(orderNo);
        } catch (Exception e) {
            e.printStackTrace();
            /**
             * 做兜底
             * 1.重试3次
             * 2.保存要取消支付单的记录到数据库，运维人员来人工跟进
             * 3.给运维人员发站内信，邮件，消息
             */
        }
    }

    /**
     * 秒杀下单
     *  1.参数校验
     * 2.token是否有效，如果有效才下单==防重复token校验
     * 3.业务校验  从redis中查询预创订单
     * 4.根据预创订单生成主订单和子订单
     * 5.保存订单 （主订单-子订单）
     * 6.删除防重复token
     * 7.删除redis中的预创订单----支付超时，支付成功之后去删除
     * @param dto
     * @return
     */
    @Override
    public String killPlaceOrder(KillCourseOrderInfosDto dto) {
        //获取预创订单
        KillPreOrderParamsDto preOrder = (KillPreOrderParamsDto) redisTemplate.opsForValue().get(dto.getOrderNo());
        AssertUtil.isNotNull(preOrder,"没有预创订单");
        //检验token
        Long loginId = 1L;
        // org.apache.commons.lang.StringUtils
        String key = preOrder.getCourseId()+":"+loginId;
        Object tokenTmp = redisTemplate.opsForValue().get(key);
        AssertUtil.isNotNull(tokenTmp,"token失效，请重新下单");
        AssertUtil.isEqualsIgnoreCase(dto.getToken(),tokenTmp.toString(),"token不一致,请重新下单");

        //4.保存订单 （主订单-子订单）
        //4.1 保存主订单  主订单中的总金额是没有
        Date date = new Date();
        CourseOrder order = new CourseOrder();
        order.setCreateTime(date);
        order.setOrderNo(preOrder.getOrderNo());
        order.setStatusOrder(0);
        order.setUserId(preOrder.getLoginId());
        order.setVersion(0);
        order.setPayType(dto.getPayType());
        order.setTotalAmount(preOrder.getKillPrice());
        order.setTotalCount(preOrder.getKillCount());
        order.setTitle("秒杀下单："+preOrder.getCourseName()); 
        //insert(order);//保存订单
        //4.2 保存子订单 统计出总金额
        CourseOrderItem item = new CourseOrderItem();
        //item.setOrderId(order.getId());
        item.setAmount(preOrder.getKillPrice());
        item.setCount(preOrder.getKillCount());
        item.setCreateTime(date);
        item.setCourseId(preOrder.getCourseId());
        item.setCourseName(preOrder.getCourseName());
        item.setCoursePic(preOrder.getCoursePic());
        item.setVersion(order.getVersion());
        item.setOrderNo(order.getOrderNo());
        //courseOrderItemService.insert(item);
        order.getItems().add(item);//把一个的子订单放入order中
        //updateById(order);


        //向mq发送事务消息==========================
        HashMap<Object, Object> extParam = new HashMap<>();
        extParam.put("userId",loginId);
        extParam.put("courseId",preOrder.getCourseId());
        PayDto payDto = new PayDto(
                order.getTotalAmount(),
                order.getPayType(),
                null,
                order.getOrderNo(),
                order.getUserId(),
                JSON.toJSONString(extParam),
                order.getTitle()
        );
        log.info("发送事务消息");
        //构建消息体
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(payDto)).build();
        TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(
                "service-order-producer",
                "course-order-topic:course-order-tag",
                //消息体
                message,
                //额外参数
                order
        );
        //发行消息后的事务状态检查
        log.info("发送完事务消息--检查事务消息状态");
        LocalTransactionState localTransactionState = transactionSendResult.getLocalTransactionState();
        SendStatus sendStatus = transactionSendResult.getSendStatus();
        if(localTransactionState != LocalTransactionState.COMMIT_MESSAGE || sendStatus != SendStatus.SEND_OK ){
            log.info("检查事务状态消息异常");
            throw new GlobleException("下单失败！！");
        }
        //==========发送事务消息结束

        //发送支付超时延迟消息
        try {
            SendResult sendResult = rocketMQTemplate.syncSend(
                    "paytimeout-topic:paytimeout-tag",//topic:tag
                    MessageBuilder.withPayload(order.getOrderNo()).build(),//消息体
                    2000,//发送延迟消息的超时时间
                    16//延迟等级  如果是30分钟 16
            );
            SendStatus sendStatus1 = sendResult.getSendStatus();
            if(sendStatus1 != SendStatus.SEND_OK){
            /*
              先尝试重试3次，如果3次重试都发不出去
                1.记录一个log表
                2.发站内信，发短信，发邮件通知运维人员，人工接入操作
             */
            }
        } catch (Exception e) {
            e.printStackTrace();
            //报错，我们需要做补偿机制
            /*
             先尝试重试3次，如果3次重试都发不出去
             1.记录一个log表
             2.发站内信，发短信，发邮件通知运维人员，人工接入操作
             */
        }
        //删除防重token
        redisTemplate.delete(key);
        System.out.println(order.getOrderNo());

        //删除预创订单 支付超时，或者支付成功去删
        //redisTemplate.delete(preOrder.getOrderNo());
        return order.getOrderNo();
    }
}
